#pragma once
#include "log_pro.hpp"
#include "epoller.hpp"
#include "socket.hpp"
#include "nocopy.hpp"
#include <functional>
#include <memory>
#include <string>
#include <fcntl.h>
#include <unordered_map>
#include "calculate.hpp"

const int maxevents = 128;

class Connection;
class Tcpserver;

using cal_back = std::function<void(std::shared_ptr<Connection>)>; // 回调函数的类型

class Connection
{
public:
    Connection(int sock) : _sock(sock) // 输入输出缓冲区这里应该不需要初始化
    {
    }

    ~Connection()
    {
        // std::cout << "close socket fd" << std::endl;
        // close(_sock);
    }

    void setCb(cal_back Read, cal_back Send, cal_back Special)
    {
        _read = Read;
        _send = Send;
        _special = Special;
    }

    void setTcpPtr(std::shared_ptr<Tcpserver> tcpserver_ptr) // 由于this指针实参是匿名对象，所以这里需要使用shared_ptr，保持匿名对象的生命周期，防止二次析构的问题
    {
        _tcpserver_ptr = tcpserver_ptr;
    }

    void setRemote(const std::string &ip, uint16_t port)
    {
        _ip = ip;
        _port = port;
    }

    int getsock()
    {
        return _sock;
    }

    void addInbuffer(const char *buffer)
    {
        _inbuffer += buffer;
    }

    void addOutbuffer(const char *buffer)
    {
        _outbuffer += buffer;
    }

    std::string &getInbuffer()
    {
        return _inbuffer;
    }

    std::string &getOutbuffer()
    {
        return _outbuffer;
    }

private:
    int _sock;              // 文件描述符
    std::string _inbuffer;  // 文件的输入缓冲区(这里使用的是string无法处理二进制流，所以是有一些需要注意的问题的)
    std::string _outbuffer; // 文件输出缓冲区

public:
    // 回调函数，因为需要根据不同类型的文件描述符分配不同的读写方式
    cal_back _read;
    cal_back _send;
    cal_back _special;

    // 远端机器的ip与port
    std::string _ip;
    uint16_t _port;

    // 还需要一个回指向tcpserver的指针(暂时不知道作用)
    std::shared_ptr<Tcpserver> _tcpserver_ptr;
};

class Tcpserver : public Nocopy
{
public:
    Tcpserver(uint16_t port, cal_back Dispose)
        : _epoll_ptr(std::make_shared<Epoller>()), _listensock_ptr(std::make_shared<Socket>()), _port(port), this_ptr(this), _dispose(Dispose)
    {
        // 初始化socket
        int opt = 1;
        setsockopt(_listensock_ptr->getsock(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        _listensock_ptr->Bind(_port);
        _listensock_ptr->Listen();
        if (_listensock_ptr->getsock() != -1)
        {
            lg(DEBUG, "listen success! fd:%d", _listensock_ptr->getsock());
        }
        if (_epoll_ptr->getFd() != -1)
        {
            lg(DEBUG, "epoll create success! fd:%d", _epoll_ptr->getFd());
        }

        // 将listenSock放入epoll和connections
        int sock = _listensock_ptr->getsock();
        InConnections(sock, EPOLLIN, std::bind(&Tcpserver::toAccpet, this, std::placeholders::_1), nullptr, nullptr); // 将toAccept函数绑定给listensock
    }

    ~Tcpserver()
    {
    }

    void setNonBlock(int fd) // 可以传递不同文件，修改不同文件为非阻塞等待
    {
        int flag = fcntl(fd, F_GETFL);
        if (flag < 0)
        {
            lg(ERROR, "fcntl file erroron:%d,%s", errno, strerror(errno));
            exit(-1);
        }
        else
        {
            fcntl(fd, F_SETFL, flag | O_NONBLOCK);
        }
    }

    void InConnections(int sock, int op /* 这个op是指关心什么事件*/, cal_back Read, cal_back Write, cal_back Spacial,
                       const std::string &ip = "0.0.0.0", uint16_t port = 10000)
    // 初始化_connections，并将sock载入到epoll中
    {
        // 首先创建出connection
        std::shared_ptr<Connection> new_connection = std::make_shared<Connection>(sock);
        // 初始化connection对象
        new_connection->setCb(Read, Write, Spacial);

        new_connection->setTcpPtr(this_ptr); // 注意这里是如果是匿名对象，这行结束后如果没有增加引用计数会被析构的
        new_connection->setRemote(ip, port);

        // 将connection对象插入_connections中
        _connections.insert(std::make_pair(sock, new_connection));
        // 将这个文件添加进入到epoll中
        _epoll_ptr->ctl(EPOLL_CTL_ADD, sock, op | EPOLLET); // 还需要设置为边缘触发模式所以带一个EPOLLET

        // 将sock设置为非阻塞等待，与边缘触发匹配
        setNonBlock(sock);
    }

    void toAccpet(std::shared_ptr<Connection> connection)
    {
        // lg(DEBUG, "读回调调用成功");
        std::string ip;
        uint16_t port;
        while (true)
        {
            int sock = _listensock_ptr->Accept(ip, &port);
            if (sock != -1)
            {
                lg(DEBUG, "[%s:%d] get a link fd:%d!", ip.c_str(), port, sock);
                // 将读取到的文件描述符写入到_connections与epoll中
                InConnections(sock, EPOLLIN,
                              std::bind(&Tcpserver::toRead, this, std::placeholders::_1),
                              std::bind(&Tcpserver::toWrite, this, std::placeholders::_1),
                              std::bind(&Tcpserver::toSpecial, this, std::placeholders::_1),
                              ip, port);
            }
            else
            {
                if (errno == EWOULDBLOCK)
                {
                    lg(DEBUG, "listen套接字读取完毕");
                    break;
                }
                if (errno == EINTR) // 如果只是被信号中断，那么还可以继续读取
                    continue;
                // 其他情况直接break
                connection->_special(connection); // 交给特殊处理函数去处理(对这个listensock其实可有可以这个特殊处理)
                break;
            }
        }
    }

    void toRead(std::shared_ptr<Connection> connection) // 处理读事件
    {
        char buffer[256];
        while (true)
        {
            memset(buffer, 0, sizeof(buffer));
            int n = read(connection->getsock(), buffer, sizeof(buffer) - 1);
            if (n == -1) // 错误处理
            {
                if (errno == EWOULDBLOCK)
                    break;
                if (errno == EINTR)
                    continue;
                // 其他错误
                //...调用toSpecial函数
                connection->_special(connection); // 交给特殊处理函数去处理
                lg(DEBUG, "read error");
                return;
            }
            else if (n == 0) // 对端关闭
            {
                // 蛋哥这里是直接交给特殊处理函数去做的，我这里直接在这进行删除sock也可以，但按照蛋哥的来吧
                // int sock = connection->getsock();
                // lg(NOTICE, "[%s:%d] remote close fd:%d", connection->_ip.c_str(), connection->_port, sock);
                // _epoll_ptr->ctl(EPOLL_CTL_DEL, sock, 0);
                // close(sock);
                // // _connections.erase(sock);//这样直接删除没问题
                // auto it = _connections.find(sock);
                // if (it != _connections.end())
                // {
                //     // 这样删除更加保险一些
                //     _connections.erase(it);
                // }
                // break;
                connection->_special(connection); // 交给特殊处理函数去处理
                return;
            }
            else
            {
                buffer[n] = '\0';
                connection->addInbuffer(buffer);
            }
        }
        // 当将数据都成功读取到了上层后，我们调用上层函数来处理传到上层的数据
        _dispose(connection);
    }

    void toWrite(std::shared_ptr<Connection> connection) // 处理写事件
    {
        int sock = connection->getsock();
        std::string &sendMsg = connection->getOutbuffer();
        while (true)
        {
            int n = write(sock, sendMsg.c_str(), sendMsg.size());
            if (n == 0)
            {
                // lg(DEBUG,"readretN=0");
                break;
            }
            else if (n == -1)
            {
                if (errno == EWOULDBLOCK)
                    break;
                if (errno == EINTR)
                    continue;

                // 或者是真正出现异常
                connection->_special(connection); // 交给特殊处理函数去处理
                break;
            }
            else
            {
                // lg(DEBUG,"发送成功");
                sendMsg.erase(0, n); // 去除掉已经发送完成的数据(消费)
            }
        }
        // 什么时候需要关心写事件？写事件不需要常关心
        // 当发现写入(发送)后缓冲区中还有数据，那就代表着写还未就绪(虽然一般写总是就绪的),此时我们才需要对写事件进行关心
        if (!sendMsg.empty())
        {
            _epoll_ptr->ctl(EPOLL_CTL_MOD, sock, EPOLLIN | EPOLLOUT | EPOLLET); // 读写关心
        }
        else
        {
            _epoll_ptr->ctl(EPOLL_CTL_MOD, sock, EPOLLIN | EPOLLET); // 将写关心取消，如果本身就没有那就当作什么也没发生
        }
    }

    void toSpecial(std::shared_ptr<Connection> connection) // 处理特殊事件
    {
        // 其实这里特殊事件处理，就是对出现异常情况的处理，不管是对端关闭，还是进程出现什么异常都进入到这个函数中
        // 这里所有情况都是进行删除sock处理，我觉得如果以后的场景变多了，就可以在函数参数中加上选项，通过选项来进
        // 行不同场景的异常处理
        int sock = connection->getsock();
        lg(NOTICE, "[%s:%d] remote close fd:%d", connection->_ip.c_str(), connection->_port, sock);
        _epoll_ptr->ctl(EPOLL_CTL_DEL, sock, 0);
        close(sock);
        auto it = _connections.find(sock);
        if (it != _connections.end())
        {
            // 这样删除更加保险一些
            _connections.erase(it);
        }
    }

    void distribute(int eventNum)
    {
        for (int i = 0; i < eventNum; i++)
        {
            // 接下来的处理与前面的epoll不同了，由于connection将不同的回调和sock关联了起来，所以我们只需要统一调用connection中回调
            // 那么我们现在需要知道的就是这个sock相对应被关心且就绪的事件是什么，我们要来处理这些事件
            int event = _ev[i].events; // 关心的事件(这个是宏标志位其中是|与了很多事件的，所以我们要通过&或来判断是否存在某些事件)
            int sock = _ev[i].data.fd; // 事件的文件描述符

            // EPOLLHUP,EPOLLERR这两种特殊事件我们将放入到读写事件中处理
            if (event & EPOLLHUP)
            {
                event |= EPOLLIN;
                event |= EPOLLOUT;
            }
            if (event & EPOLLERR)
            {
                event |= EPOLLIN;
                event |= EPOLLOUT;
            }

            // 这里保存这个conn是最重要的一步，因为现在只有一个shared_ptr在管理这个sock，
            // 那么如果在read函数或者write函数中，对_connections中的智能指针进行了删除，也不会导致文件描述符被释放，
            // 也就是文件描述符的关闭被滞后了，只有当最后一个shared_ptr释放的时候，才会对sock文件描述符进行释放，
            // 也就是这个我保存的conn，如果关闭连接的话，他就是最后一个指向connection的智能指针，
            // 出了这个作用域之后，才会调用connection的析构函数，对文件描述符进行释放
            std::shared_ptr<Connection> conn = _connections[sock]; // 最好是创建一个智能指针指向一个对象，其他时候要创建智能指针时就拷贝这个唯一的智能指针，防止多次析构问题出现

            // 处理不同的事件
            if (event & EPOLLIN && conn) // 关心的读事件就绪,再加一个判断条件防止我们的这个conn不存在
            {
                //lg(DEBUG, "读回调");
                // 加一些判断条件防止调用空指针
                // if (_connections[sock]->_read) {
                //     _connections[sock]->_read(std::shared_ptr<Connection>(_connections[sock]));
                // }
                if (conn->_read)
                    conn->_read(conn); // 运行connection中存储的读回调
            }
            if (event & EPOLLOUT && conn) // 关心写事件就绪,再加一个判断条件防止我们的这个conn不存在
            {
                //lg(DEBUG, "写回调");
                // if (_connections[sock]->_read) {
                //     _connections[sock]->_read(std::shared_ptr<Connection>(_connections[sock]));
                // }
                if (conn->_send)
                    conn->_send(conn); // 运行connection中存储的写回调
            }
        }
    }

    void show_connections()
    {
        std::cout << "_connections list: ";
        for (auto &it : _connections)
        {
            std::cout << it.first << ": " << it.second->getOutbuffer() << " ";
        }
        std::cout << std::endl;
    }

    void start()
    {
        while (true)
        {
            int n = _epoll_ptr->wait(_ev, maxevents);
            if (n == -1)
            {
                lg(ERROR, "epoll_wait fail %d: %s", errno, strerror(errno));
                exit(-1);
            }
            else if (n == 0)
            {
                lg(NOTICE, "文件未就绪");
                continue;
            }
            else
            {
                distribute(n); // 派发任务
                // show_connections();//debug
            }
        }
    }

private:
    std::shared_ptr<Epoller> _epoll_ptr;
    std::shared_ptr<Socket> _listensock_ptr;
    uint16_t _port;
    std::shared_ptr<Tcpserver> this_ptr;//存一个this指针的shared_ptr，让其他需要获取this智能指针的直接拷贝
    struct epoll_event _ev[maxevents];                                 // 装载就绪队列的数组
    std::unordered_map<int, std::shared_ptr<Connection>> _connections; // connection对象与sock的键值对
    cal_back _dispose;                                                 // 用来处理获得的数据的回调函数
};